%{
// Thud - a dynamic object-oriented language
// Copyright (C) 2009,2010 David H. Hovemeyer <david.hovemeyer@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// Parser - builds an abstract syntax tree of a translation unit

#include <cassert>
#include <cwchar>
#include "fe/ASTNode.h"
#include "fe/yyparse.h"
#include "fe/Parser.h"
#include "fe/ParserFunctions.h"
#include "fe/Token2.h"
#include "fe/Lexer2.h"
#include "util/String.h"

// The Parser context data structure stores a pointer
// to the Lexer state data structure.
#define YYLEX_PARAM context->getScanner()

#define AST_SET_CURRENT_LOC(node) \
do { \
	(node)->setLocation(context->getScanner()->getCurrentLocation()); \
} while (0)

%}

%pure-parser
%parse-param { Parser* context }

%union {
	Token2 *tok;
	wchar_t *sval;
	int ival;
	ASTNode *ast;
}

/* Terminal symbols */
%token<tok> METHOD FIELD CLASS NEW SELF VAR WHILE IF ELSE IMPORT AS MODULE RETURN NIL MAGIC
%token<tok> IDENT SYMBOL_LITERAL STRING_LITERAL
%token<tok> INT_LITERAL TRUE FALSE
%token<tok> DOT COMMA DOUBLECOLON COLON SEMICOLON LPAREN RPAREN LBRACE RBRACE
%token<tok> NOT

%right    ASSIGN
%left     LOGICAL_AND LOGICAL_OR
%left     EQUALS NOT_EQUALS LESS_THAN LESS_THAN_EQ GREATER_THAN GREATER_THAN_EQ
%left     PLUS MINUS
%left     TIMES DIVIDE

%type<ast> unit opt_import_list import_list import_directive statement_list statement
%type<ast> opt_else_clause
%type<ast> expression member_or_call_expression call_args simple_expression 
%type<ast> opt_arg_list arg_list opt_member_list member_list member
%type<ast> named_method opt_param_list var_list var
%type<ast> var_with_opt_init_list var_with_opt_init opt_init
%type<ast> opt_statement_list
%type<ast> field
%type<ast> unary_expression

%type<sval> opt_as_clause qualified_class_name

%type<tok> class_or_module
%type<ival> bin_op
%type<sval> ident_or_bin_op

%{
// This is defined in LexerFunctions.cpp.
int yylex(YYSTYPE *, Lexer2 *);
%}

%%

unit :
	  opt_import_list statement_list
		{
			$$ = ASTNode::alloc(AST_UNIT, $1->getLocation());
			$$->appendChild($1);
			$$->appendChild($2);
			
			context->setAST($$);
		}
	;

opt_import_list :
	  /* epsilon */
		{
			$$ = ASTNode::alloc(AST_IMPORT_LIST, context->getScanner()->getCurrentLocation());
			AST_SET_CURRENT_LOC($$);
		}
	| import_list
		{
			$$ = ASTNode::alloc(AST_IMPORT_LIST, $1->getLocation());
			$$->appendChild($1);
		}
	;

import_list :
	  import_directive
		{
			$$ = ASTNode::alloc(AST_IMPORT_LIST, $1->getLocation());
			$$->appendChild($1);
		}
	| import_directive import_list
		{
			$$ = $2;
			$$->prependChild($1);
		}
	;

import_directive :
	  IMPORT qualified_class_name opt_as_clause SEMICOLON
		{
			$$ = ASTNode::alloc(AST_IMPORT, $1->getLocation());
			wchar_t *clsName = $2;
			wchar_t *alias = $3;
			Import *import = alias ? Import::alloc(clsName, alias) : Import::alloc(clsName);
			$$->adoptImport(import);

			String::wdestroy($2);
			String::wdestroy($3);
		}
	| IMPORT qualified_class_name METHOD IDENT SEMICOLON
		{
			$$ = ASTNode::alloc(AST_STATIC_IMPORT, $1->getLocation());
			wchar_t *clsName = $2;
			Import *import = Import::allocStatic(clsName, $4->getLexeme());
			$$->adoptImport(import);
			
			String::wdestroy($2);
		}
	;

opt_as_clause :
	  /* epsilon */
		{
			$$ = 0;
		}
	| AS IDENT
		{
			$$ = String::wdup($2->getLexeme().c_str());
		}
	;

statement_list :
	  statement
		{
			$$ = ASTNode::alloc(AST_STATEMENT_LIST, $1->getLocation());
			$$->appendChild($1);
		}
	| statement statement_list
		{
			$$ = $2;
			$$->prependChild($1);
		}
	;

statement :
	  expression SEMICOLON
		{
			$$ = ASTNode::alloc(AST_STATEMENT, $1->getLocation());
			$$->appendChild($1);
		}
	| VAR var_with_opt_init_list SEMICOLON
		{
			$$ = $2;
		}
	| class_or_module qualified_class_name LBRACE opt_member_list RBRACE
		{
			$$ = ASTNode::alloc($1->getTokenType() == CLASS ? AST_CLASS : AST_MODULE, $1->getLocation());
			$$->setStringValue($2);
			$$->appendChild($4);
			
			String::wdestroy($2);
		}
	| WHILE LPAREN expression RPAREN LBRACE statement_list RBRACE
		{
			$$ = ASTNode::alloc(AST_WHILE_STATEMENT, $1->getLocation());
			$$->appendChild($3);
			$$->appendChild($6);
		}
	| IF LPAREN expression RPAREN LBRACE statement_list RBRACE opt_else_clause
		{
			$$ = ASTNode::alloc(AST_IF_STATEMENT, $1->getLocation());
			$$->appendChild($3);
			$$->appendChild($6);
			if ($8) {
				$$->appendChild($8);
			}
		}
	| RETURN expression SEMICOLON
		{
			$$ = ASTNode::alloc(AST_RETURN_STATEMENT, $1->getLocation());
			$$->appendChild($2);
		}
	;
	
opt_else_clause :
	  /* epsilon */
		{
			$$ = 0;
		}
	| ELSE LBRACE statement_list RBRACE
		{
			$$ = $3;
		}
	;

qualified_class_name :
	  IDENT
		{
			$$ = String::wdup($1->getLexeme().c_str());
		}
	| IDENT DOUBLECOLON qualified_class_name
		{
			std::wstring qualName;
			qualName += $1->getLexeme();
			qualName += L"::";
			qualName += $3;
			
			$$ = String::wdup(qualName.c_str());

			String::wdestroy($3);
		}
	;

class_or_module :
	  CLASS
		{
			$$ = $1;
		}
	| MODULE
		{
			$$ = $1;
		}
	;

expression :
	  expression ASSIGN expression
		{
			$$ = makeBinOpASTNode($1, ASSIGN, $3);
		}
	| expression LOGICAL_AND expression
		{
			$$ = makeBinOpASTNode($1, LOGICAL_AND, $3);
		}
	| expression LOGICAL_OR expression
		{
			$$ = makeBinOpASTNode($1, LOGICAL_OR, $3);
		}
	| expression EQUALS expression
		{
			$$ = makeBinOpASTNode($1, EQUALS, $3);
		}
	| expression NOT_EQUALS expression
		{
			$$ = makeBinOpASTNode($1, NOT_EQUALS, $3);
		}
	| expression LESS_THAN expression
		{
			$$ = makeBinOpASTNode($1, LESS_THAN, $3);
		}
	| expression LESS_THAN_EQ expression
		{
			$$ = makeBinOpASTNode($1, LESS_THAN_EQ, $3);
		}
	| expression GREATER_THAN expression
		{
			$$ = makeBinOpASTNode($1, GREATER_THAN, $3);
		}
	| expression GREATER_THAN_EQ expression
		{
			$$ = makeBinOpASTNode($1, GREATER_THAN_EQ, $3);
		}
	| expression PLUS expression
		{
			$$ = makeBinOpASTNode($1, PLUS, $3);
		}
	| expression MINUS expression
		{
			$$ = makeBinOpASTNode($1, MINUS, $3);
		}
	| expression TIMES expression
		{
			$$ = makeBinOpASTNode($1, TIMES, $3);
		}
	| expression DIVIDE expression
		{
			$$ = makeBinOpASTNode($1, DIVIDE, $3);
		}
	| unary_expression
		{
			$$ = $1;
		}
	;

unary_expression :
	  member_or_call_expression
		{
			$$ = $1;
		}
	| NOT unary_expression
		{
			$$ = ASTNode::alloc(AST_UNARY_OP_EXPRESSION, $1->getLocation());
			$$->setIntValue($1->getTokenType());
			$$->appendChild($2);
		}
	;

bin_op :
	  LOGICAL_AND		{ $$ = LOGICAL_AND; }
	| LOGICAL_OR		{ $$ = LOGICAL_OR; }
	| EQUALS		{ $$ = EQUALS; }
	| NOT_EQUALS		{ $$ = NOT_EQUALS; }
	| LESS_THAN		{ $$ = LESS_THAN; }
	| LESS_THAN_EQ		{ $$ = LESS_THAN_EQ; }
	| GREATER_THAN		{ $$ = GREATER_THAN; }
	| GREATER_THAN_EQ	{ $$ = GREATER_THAN_EQ; }
	| PLUS			{ $$ = PLUS; }
	| MINUS			{ $$ = MINUS; }
	| TIMES			{ $$ = TIMES; }
	| DIVIDE		{ $$ = DIVIDE; }
	;

member_or_call_expression :
	  member_or_call_expression DOT IDENT
		{
			$$ = ASTNode::alloc(AST_FIELD_REF_EXPR, $1->getLocation());
			$$->appendChild($1);
			$$->setStringValueFromToken($3);
		}
	| member_or_call_expression DOT IDENT call_args
		{
			$$ = ASTNode::alloc(AST_METHOD_CALL_EXPR, $1->getLocation());
			$$->appendChild($1);
			$$->setStringValueFromToken($3);
			$$->appendChild($4);
		}
	| member_or_call_expression DOT bin_op call_args
		{
			$$ = ASTNode::alloc(AST_METHOD_CALL_EXPR, $1->getLocation());
			$$->appendChild($1);
			$$->setStringValue(binOpToString($3));
			$$->appendChild($4);
		}
	| simple_expression
	;

call_args :
	  LPAREN opt_arg_list RPAREN
		{
			$$ = $2;
		}
	;

simple_expression :
	  IDENT
		{
			$$ = ASTNode::alloc(AST_VAR_REF_EXPR, $1->getLocation());
			$$->setStringValueFromToken($1);
		}
	| IDENT call_args
		{
			$$ = ASTNode::alloc(AST_MODULE_METHOD_CALL_EXPR, $1->getLocation());
			$$->setStringValueFromToken($1); // name of called method
			$$->appendChild($2);
		}
	| SYMBOL_LITERAL
		{
			$$ = ASTNode::alloc(AST_SYMBOL_LITERAL_EXPR, $1->getLocation());
			$$->setStringValueFromToken($1);
		}
	| COLON STRING_LITERAL
		{
			$$ = ASTNode::alloc(AST_SYMBOL_LITERAL_EXPR, $1->getLocation());
			$$->setStringValueFromToken($2);
		}
	| STRING_LITERAL
		{
			$$ = ASTNode::alloc(AST_STRING_LITERAL_EXPR, $1->getLocation());
			//$$->setStringValueFromToken($1); /* TODO: process escape sequences */
			$$->setStringValueFromStringLiteral($1);
		}
	| INT_LITERAL
		{
			$$ = ASTNode::alloc(AST_INT_LITERAL_EXPR, $1->getLocation());
			$$->setIntValue(String::wsToInt($1->getLexeme().c_str())); // FIXME
		}
	| TRUE
		{
			$$ = ASTNode::alloc(AST_BOOLEAN_LITERAL_EXPR, $1->getLocation());
			$$->setIntValue(1);
			
		}
	| FALSE
		{
			$$ = ASTNode::alloc(AST_BOOLEAN_LITERAL_EXPR, $1->getLocation());
			$$->setIntValue(0);
			
		}
	| SELF
		{
			$$ = ASTNode::alloc(AST_SELF_REF_EXPR, $1->getLocation());
		}
	| NIL
		{
			$$ = ASTNode::alloc(AST_NIL_EXPR, $1->getLocation());
		}
	| NEW qualified_class_name LPAREN opt_arg_list RPAREN
		{
			$$ = ASTNode::alloc(AST_NEW_EXPR, $1->getLocation());
			$$->setStringValue(std::wstring($2));
			String::wdestroy($2);
			$$->appendChild($4);
		}
	| LPAREN expression RPAREN
		{
			$$ = $2;
		}
	;

opt_arg_list :
	  /* epsilon */
		{
			$$ = ASTNode::alloc(AST_ARG_LIST, context->getScanner()->getCurrentLocation());
		}
	| arg_list
		{
			$$ = $1;
		}
	;

arg_list :
	  expression
		{
			$$ = ASTNode::alloc(AST_ARG_LIST, $1->getLocation());
			$$->appendChild($1);
		}
	| expression COMMA arg_list
		{
			$$ = $3;
			$$->prependChild($1);
		}
	;

opt_member_list :
	  /* epsilon */
		{
			$$ = ASTNode::alloc(AST_MEMBER_LIST, context->getScanner()->getCurrentLocation());
		}
	| member_list
		{
			$$ = $1;
		}
	;

member_list :
	  member
		{
			$$ = ASTNode::alloc(AST_MEMBER_LIST, $1->getLocation());
			$$->appendChild($1);
		}
	| member member_list
		{
			$$ = $2;
			$$->prependChild($1);
		}
	;

member :
	  named_method
		{
			$$ = $1;
		}
	| field SEMICOLON
		{
			$$ = $1;
		}
	;

named_method :
	  METHOD ident_or_bin_op LPAREN opt_param_list RPAREN LBRACE opt_statement_list RBRACE
		{
			$$ = ASTNode::alloc(AST_METHOD, $1->getLocation());
			$$->appendChild($4);
			$$->appendChild($7);
			$$->setStringValue($2);
			String::wdestroy($2);
		}
	| MAGIC METHOD ident_or_bin_op LPAREN opt_param_list RPAREN SEMICOLON
	  {
			$$ = ASTNode::alloc(AST_MAGIC_METHOD, $1->getLocation());
			$$->appendChild($5);
			$$->setStringValue($3);
			String::wdestroy($3);
	  }
	;

ident_or_bin_op :
	  IDENT
		{
			$$ = String::wdup($1->getLexeme().c_str());
		}
	| EQUALS
		{
			$$ = String::wdup(L"==");
		}
	| NOT_EQUALS
		{
			$$ = String::wdup(L"!=");
		}
	| LESS_THAN
		{
			$$ = String::wdup(L"<");
		}
	| LESS_THAN_EQ
		{
			$$ = String::wdup(L"<=");
		}
	| GREATER_THAN
		{
			$$ = String::wdup(L">");
		}
	| GREATER_THAN_EQ
		{
			$$ = String::wdup(L">=");
		}
	| PLUS
		{
			$$ = String::wdup(L"+");
		}
	| MINUS
		{
			$$ = String::wdup(L"-");
		}
	| TIMES
		{
			$$ = String::wdup(L"*");
		}
	| DIVIDE
		{
			$$ = String::wdup(L"/");
		}
	;

opt_param_list :
	  /* epsilon */
		{
			$$ = ASTNode::alloc(AST_VAR_LIST, context->getScanner()->getCurrentLocation());
		}
	| var_list
		{
			$$ = $1;
		}
	;

var_list :
	  var
		{
			$$ = ASTNode::alloc(AST_VAR_LIST, $1->getLocation());
			$$->appendChild($1);
		}
	| var COMMA var_list
		{
			$$ = $3;
			$$->prependChild($1);
		}
	;

var :
	  IDENT
		{
			$$ = ASTNode::alloc(AST_VAR, $1->getLocation());
			$$->setStringValueFromToken($1);
		}
	;

var_with_opt_init_list :
	  var_with_opt_init
		{
			$$ = ASTNode::alloc(AST_VAR_LIST, $1->getLocation());
			$$->appendChild($1);
		}
	| var_with_opt_init COMMA var_with_opt_init_list 
		{
			$$ = $3;
			$$->prependChild($1);
		}
	;

var_with_opt_init :
	  IDENT opt_init
		{
			$$ = ASTNode::alloc(AST_VAR, $1->getLocation());
			$$->setStringValueFromToken($1);
			if ($2 != 0) {
				$$->appendChild($2);
			}
		}
	;

opt_init :
	  ASSIGN expression
		{
			$$ = $2;
		}
	| /* epsilon */
		{
			$$ = 0;
		}
	;

opt_statement_list :
	  /* epsilon */
		{
			$$ = ASTNode::alloc(AST_STATEMENT_LIST, context->getScanner()->getCurrentLocation());
		}
	| statement_list
		{
			$$ = $1;
		}
	;

field :
	  FIELD IDENT
		{
			$$ = ASTNode::alloc(AST_FIELD, $1->getLocation());
			$$->setStringValueFromToken($2);
			$$->setLocation($1->getLocation());
		}
	;

%%

